<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <canvas id="glCanvas" width="800" height="600"></canvas>

    <script type="module">
      import { mat4 } from "gl-matrix";
      class BoxGeometry {
        constructor(canvasId, width = 1, height = 1, depth = 1) {
          this.canvas = document.getElementById(canvasId);
          this.gl = this.canvas.getContext("webgl");
          this.dimensions = { width, height, depth };
          this.rotation = { x: 0, y: 0 };

          this.initGL();
          this.createBuffers();
          this.setupShaders();
          this.setupMatrices();
        }

        // 初始化WebGL上下文‌:ml-citation{ref="3,5" data="citationList"}
        initGL() {
          this.gl.enable(this.gl.DEPTH_TEST);
          this.gl.clearColor(0.1, 0.1, 0.1, 1.0);
        }

        // 生成完整顶点和索引数据‌:ml-citation{ref="1,2" data="citationList"}
        createBuffers() {
          const w = this.dimensions.width / 2,
            h = this.dimensions.height / 2,
            d = this.dimensions.depth / 2;

          // 完整顶点数据（6个面 × 4顶点 × 6分量）
          const verticesColors = new Float32Array([
            // 前表面（白色）
            -w,
            -h,
            d,
            1,
            1,
            1, // 0
            w,
            -h,
            d,
            1,
            1,
            1, // 1
            w,
            h,
            d,
            1,
            1,
            1, // 2
            -w,
            h,
            d,
            1,
            1,
            1, // 3

            // 后表面（红色）
            -w,
            -h,
            -d,
            1,
            0,
            0, // 4
            w,
            -h,
            -d,
            1,
            0,
            0, // 5
            w,
            h,
            -d,
            1,
            0,
            0, // 6
            -w,
            h,
            -d,
            1,
            0,
            0, // 7

            // 左表面（蓝色）
            -w,
            -h,
            -d,
            0,
            0,
            1, // 8
            -w,
            -h,
            d,
            0,
            0,
            1, // 9
            -w,
            h,
            d,
            0,
            0,
            1, // 10
            -w,
            h,
            -d,
            0,
            0,
            1, // 11

            // 右表面（黄色）
            w,
            -h,
            d,
            1,
            1,
            0, // 12
            w,
            -h,
            -d,
            1,
            1,
            0, // 13
            w,
            h,
            -d,
            1,
            1,
            0, // 14
            w,
            h,
            d,
            1,
            1,
            0, // 15

            // 顶部（绿色）
            -w,
            h,
            d,
            0,
            1,
            0, // 16
            w,
            h,
            d,
            0,
            1,
            0, // 17
            w,
            h,
            -d,
            0,
            1,
            0, // 18
            -w,
            h,
            -d,
            0,
            1,
            0, // 19

            // 底部（紫色）
            -w,
            -h,
            -d,
            1,
            0,
            1, // 20
            w,
            -h,
            -d,
            1,
            0,
            1, // 21
            w,
            -h,
            d,
            1,
            0,
            1, // 22
            -w,
            -h,
            d,
            1,
            0,
            1, // 23
          ]);

          // 完整索引数据（6个面 × 6索引）
          const indices = new Uint16Array([
            0,
            1,
            2,
            0,
            2,
            3, // 前
            4,
            5,
            6,
            4,
            6,
            7, // 后
            8,
            9,
            10,
            8,
            10,
            11, // 左
            12,
            13,
            14,
            12,
            14,
            15, // 右
            16,
            17,
            18,
            16,
            18,
            19, // 顶
            20,
            21,
            22,
            20,
            22,
            23, // 底
          ]);

          // 创建缓冲区对象‌:ml-citation{ref="2,3" data="citationList"}
          this.vertexBuffer = this.gl.createBuffer();
          this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.vertexBuffer);
          this.gl.bufferData(this.gl.ARRAY_BUFFER, verticesColors, this.gl.STATIC_DRAW);

          this.indexBuffer = this.gl.createBuffer();
          this.gl.bindBuffer(this.gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer);
          this.gl.bufferData(this.gl.ELEMENT_ARRAY_BUFFER, indices, this.gl.STATIC_DRAW);
        }

        // 着色器初始化‌:ml-citation{ref="1,2" data="citationList"}
        setupShaders() {
          const vsSource = `
            attribute vec3 aPosition;
            attribute vec3 aColor;
            uniform mat4 uMVP;
            varying vec3 vColor;
            void main() {
                gl_Position = uMVP * vec4(aPosition, 1.0);
                vColor = aColor;
            }`;

          const fsSource = `
            precision mediump float;
            varying vec3 vColor;
            void main() {
                gl_FragColor = vec4(vColor, 1.0);
            }`;

          this.program = this.createProgram(vsSource, fsSource);
          this.gl.useProgram(this.program);

          this.attribs = {
            position: this.gl.getAttribLocation(this.program, "aPosition"),
            color: this.gl.getAttribLocation(this.program, "aColor"),
          };
          this.uniforms = {
            mvp: this.gl.getUniformLocation(this.program, "uMVP"),
          };
        }

        // 创建着色器程序‌:ml-citation{ref="2,3" data="citationList"}
        createProgram(vs, fs) {
          const program = this.gl.createProgram();
          [this.gl.VERTEX_SHADER, this.gl.FRAGMENT_SHADER].forEach((type, idx) => {
            const shader = this.gl.createShader(type);
            this.gl.shaderSource(shader, [vs, fs][idx]);
            this.gl.compileShader(shader);
            this.gl.attachShader(program, shader);
          });
          this.gl.linkProgram(program);
          return program;
        }

        // 矩阵初始化‌:ml-citation{ref="4,5" data="citationList"}
        setupMatrices() {
          const aspect = this.canvas.width / this.canvas.height;

          // 投影矩阵（透视投影）
          this.projection = mat4.create();
          mat4.perspective(this.projection, Math.PI / 3, aspect, 0.1, 100.0);

          // 视图矩阵（相机位置）
          this.view = mat4.create();
          mat4.translate(this.view, this.view, [0, 0, -5]);

          // 模型矩阵（初始单位矩阵）
          this.model = mat4.create();
        }

        // 更新旋转角度‌:ml-citation{ref="3,4" data="citationList"}
        update() {
          this.rotation.x += 0.01;
          this.rotation.y += 0.005;
        }

        // 渲染循环‌:ml-citation{ref="2,5" data="citationList"}
        render() {
          this.gl.clear(this.gl.COLOR_BUFFER_BIT | this.gl.DEPTH_BUFFER_BIT);

          // 计算模型视图投影矩阵
          mat4.identity(this.model);
          mat4.rotateX(this.model, this.model, this.rotation.x);
          mat4.rotateY(this.model, this.model, this.rotation.y);

          const mvp = mat4.create();
          mat4.multiply(mvp, this.projection, this.view);
          mat4.multiply(mvp, mvp, this.model);

          // 设置顶点属性指针
          this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.vertexBuffer);
          this.gl.vertexAttribPointer(this.attribs.position, 3, this.gl.FLOAT, false, 24, 0);
          this.gl.enableVertexAttribArray(this.attribs.position);

          this.gl.vertexAttribPointer(this.attribs.color, 3, this.gl.FLOAT, false, 24, 12);
          this.gl.enableVertexAttribArray(this.attribs.color);

          // 传递矩阵并绘制
          this.gl.uniformMatrix4fv(this.uniforms.mvp, false, mvp);
          this.gl.drawElements(this.gl.TRIANGLES, 36, this.gl.UNSIGNED_SHORT, 0);
        }
      }

      // 使用示例
      const cube = new BoxGeometry("glCanvas", 2, 2, 2);

      function animate() {
        cube.update();
        cube.render();
        requestAnimationFrame(animate);
      }
      animate();
    </script>
  </body>
</html>
